#ifndef __WZTK_AUTO_MEM_H__
#define __WZTK_AUTO_MEM_H__

#include <string.h>
#include <memory>
#include "wztypes.h"
#include <string>

// 需要zlib的支持。
#ifdef USE_ZLIB
//#include "zlib/zlib.h"
//#define MEM_ZLIB
#endif

#define WZ_MUST_LEN(len, lessLen)	((len % lessLen == 0)? len : len + lessLen - len %lessLen)

#ifndef wzmax
#define wzmin(x,y) ((x)>(y)? (y):(x))
#define wzmax(x,y) ((x)<(y)? (y):(x))
#endif

#ifndef ASSERT
#define ASSERT(...)
#endif


/**
 * @brief The CWZAutoMem class
 * 在C++中，二进制数据的序列化一直是一个麻烦事。常用的自带数据格式的序列化方法有
 * protobuf,JSON。这里提供了一种无格式的简单的序列化方法，即使用者需要知道数据
 * 存放的顺序和对应数据格式（对于整形数字即它的位数）。同时这个类提供了简单的内存
 * 管理，以避免发生麻烦的内存泄漏问题。
 * 可以将它用在一些简单的场合，对于一般的C/S通信和Pipe通信，这个类已足够可用。
 * 使用方法很简单，创建一个栈对象，然后调用WriteXXX系列函数序列化数据。然后，在
 * 需要解析数据的地方创建创建另一个对象，然后使用ReadXXX系列函数反序列化。但对象
 * 不在其作用域后，将自动释放其内部的内存。
 * 一定要注意：在WriteXXX系列写入前需要保证有足够的空间。但空间不足时需要调用
 * ChgLen/ChgLenAndSeek重置内部空间缓冲区的大小。
 */

class CWZAutoMem
{
public:
    // 获取可操作的总长度。
    int GetLen();
    // 获取当前游标的位置，使用Write系列函数都会使游标后移。
    int GetCurSeek();
    BYTE *GetBuffer();
    BYTE *GetCurBuffer();

    enum { begin = 1, current, end };

#ifdef TRACE_WZAUTOMEM_OBJECT_COUNT
    static int m_nRefCount;
#endif
    CWZAutoMem(int len = 0, BOOL bDelete = TRUE, BOOL bZero = TRUE);
    CWZAutoMem(const void *buffer, int len , bool bDelete, BOOL bZero = TRUE);
    ~CWZAutoMem(void);
    // 设置内存分配粒度，当内存不足时，每次至少分配这么多个字节。
    void SetLessMem(int n);
    int GetLessMem(){return m_nMemLess;}

    int GetNewLen();

    int SeekToBegin();
    int SeekToEnd();
    int Seek(int nSeek, int nType = current);

    void MemSet(BYTE by){ memset(m_pBuffer, by, m_nLen); }
    void ZeroMem(){MemSet(0);}
    // 一定要注意：在写入前需要保证有足够的空间。但空间不足时需要调用ChgLen/ChgLenAndSeek
    // 重置内部空间缓冲区的大小。
    int WriteByte(BYTE nByte);
    int WriteChar(char ch);
    int WriteInt(int nInt);
    int WriteUINT(UINT n);
    int WriteUSHORT(USHORT nUshort);
    int WriteShort(short nShort);
    int WriteLONGLONG(LONGLONG ll) {return Write(&ll, 8);}
    // 写入一段随机值到内部缓冲区中。
    int WriteRand(int nSize);

    int WriteData(unsigned long n);
    int WriteData(long n);
    int WriteData(BYTE nByte);
    int WriteData(char ch);
    int WriteData(int nInt);
    int WriteData(UINT n);
    int WriteData(USHORT nUshort);
    int WriteData(short nShort);

    // 大尾数（网络字节序）
    int WriteIntHL(int nInt);
    int WriteUSHORTHL(USHORT nUshort);
    // 返回成功写入的长度，使用者需要确保长度足够，否则可能写入失败或者少写。
    int Write(const void *buffer, int len);
    // 如果长度不够就自动重新分配内存添加长度。
    int WriteMust(const void *buf, int len);
    int Read(void *buffer, int len);
    // 注：如使用大尾数（网络字节序），则参数为false。
    int ReadInt( bool bLowBit = true );
    LONGLONG ReadLongLong( );
    // 注：如使用大尾数（网络字节序），则参数为false。
    short ReadShort( bool bLowBit = true);
    // 注：如使用大尾数（网络字节序），则参数为false。
    USHORT ReadUShort( bool bLowBit = true);

    BYTE ReadByte();

    std::string ReadLenString();
    // 将字符串写入到对象中，用这种方式写入时会同时添加长度信息，因此不需要将尾部的NULL写入。
    void WriteLenString(const char* lpsz);

    //把一段内存附加进来这个类.
    //bDelete 当这个类销毁时,是否要delet这段内存.
    int Attach(const void *buffer, int len, BOOL bDelete = FALSE);

    //将会把这个类里面的数据用Zlib进行压缩一次.
    BOOL ZlibMem();
    BOOL UnZlibMem();	//和上面相反

    //销毁所有内存。清空一切数据.m_bDelete = TRUE 时会删掉内存.
    void Destory();
    void Init(int len = 0, BOOL bDelete = TRUE);

    BOOL ChgLen(int nNewLen, BOOL bDelNew = TRUE);
    BOOL ChgLenAndSeek(int nNewLen, int nSeekFromBegin);

    //返回沒有操作的長度
    int GetNoUseLen();

    //查找下一段内存,找不到返回-1
    int FindNext(void *buffer, int len, BOOL bFindBegin = FALSE);

    // 替换所有bufferOld为buffernew，返回成功替换的次数。
    int RepLace(void *bufferOld, void *buffernew, int len);

    // 判断时候以这个内存的值开始。
    BOOL StartWith(const void *buf, int len);

    // 从当前偏移开始开始找，确定是否有字符串结束位'\0'
    BOOL HasStringFromCur();

    // 将自身内容复制到pMem指向的对象中。
    // 如果pMem的内存不足，这里会将它调整到合适的大小以容纳数据。注意此时pMem对象的内存
    // 管理特性会发生改变，将被置为对象销毁时自动释放内存（而原有内存则会根据之前的特性
    // 决定是否释放）。为避免这种复杂情况发生，在使用这个函数前应确定本对象的内存大小，
    // 然后创建大于或等于此大小的CWZAutoMem对象，再以它作为此函数的参数。
    BOOL Copy(CWZAutoMem * pMem);

    // 克隆一个新的CWZAutoMem，这个对象的内部缓冲区在该对象被销毁时自动释放（与this对象
    // 的内存管理策略无关）。
    CWZAutoMem *Clone();

    // 返回新的智能指针对象，以默认方式构造它（自动释放内存），设置内存增长粒度为32字节。
    static std::shared_ptr<CWZAutoMem> NewMem(int nLen = 0);
    static std::shared_ptr<CWZAutoMem> NewMem(const void *buf, int nLen);

    // (不常用，未来可能会移除)将内存数据[buffer, buffer+len)移动到(buffer+offset)的地方。
    static void OffestMem(void *buffer, int len, int offest);

    // (不常用，未来可能会移除)移动[当前内存点, 当前内存点+nLen)的这一段数据到(当前内存点+offset)的地方。
    // 调用者需要确定长度足够，否则会触发ASSERT（如Release版本则不会作任何事直接返回）。
    void OffestCur(int nLen, int offest);

    // (不常用，未来可能会移除)将字符串写入到对象中，包含尾部的NULL，尽量采用WriteLenString而不是这个函数。
    int WriteStr(const char* lpsz);

    // (不常用，未来可能会移除)将字符串写入到对象中，包含尾部的NULL，尽量采用WriteLenString而不是这个函数。
    int WriteData(const char* lpsz);

    // (不常用，未来可能会移除)可以按顺序索引字符串，调用这个函数后，如果返回TRUE则可使用GetCurBuffer()获得指向内部字符串的指针。
    // @Note:尽量不要使用这个函数，除非如果内部存放的是WriteStr或者WriteData(const char* lpsz)写入的字符串。
    // 如有可能，尽量避免使用直接写入字符串的WriteStr和WriteData(const char* lpsz)而是改为
    // WriteLenString并且使用ReadLenString反序列化。
    BOOL SeekToNextStr();

    //(不常用，未来可能会移除)改变内存的顺序,倒序
    static void ChgOrder(void *buffer, int len);

    // bDelete 当这个类销毁时,是否要delet这段内存.
    // @TODO: 应将此函数移除，可能会误导使用者，一个对象的内存管理策略应是不变的。
    void SetDelete(BOOL bDelete);

private:
    // 内部缓冲区。
    BYTE * m_pBuffer;
    // 游标，当前操作到哪个位置。可操作长度为[0, m_nSeek)
    int m_nSeek;
    // 内部缓冲区可操作的长度，也是对外宣称的长度。实际长度为m_nNewLen但你永远不应在外部使用这个值。
    int m_nLen;
    // 已经分配了的内存，即m_pBuffer从系统中申请的实际长度。
    // 如超过此长度则会导致缓冲区异常！所以请尽量不要直接写入GetBuffer
    // 和GetCurBuffer返回的缓冲区！
    int m_nNewLen;
    // 超过多少内存才重新分配，这个值决定了内存分配粒度。
    int m_nMemLess;
    // 如为1则在对象销毁时自动释放内部缓冲区。
    BYTE m_bDelete:1;
    // 如为1则在对象构造时自动为缓冲区置0。
    BYTE m_bZero;
};


#endif //__WZTK_AUTO_MEM_H__
